/****************************************************************************
Copyright (c) 2020 - 2022 Qualcomm Technologies International, Ltd.


FILE NAME
    a2dp_sep_handler.c

DESCRIPTION
    This file contains A2DP codec related functionality.

NOTES

*/


/****************************************************************************
    Header files
*/
#include "a2dp_profile_codec_handler.h"
#include "a2dp_profile_data_block.h"
#include "a2dp_profile_caps_parse.h"
#include "a2dp_profile_codec_aac.h"

#include "a2dp_profile_codec_sbc.h"
#include "a2dp_profile_codec_csr_aptx.h"
#include "a2dp_profile_codec_csr_aptxhd.h"
#include "a2dp_profile_codec_aptx_adaptive.h"

#include "a2dp_profile.h"

#include <print.h>
#include <string.h>
#include <stdlib.h>
#include <panic.h>
#include "logging.h"

#ifndef A2DP_SBC_ONLY


/** Helper function to check if the CODEC capabilities definition matches 
 *  the specified vendor & codec definitions.
 */
static bool checkVendorCodec(const uint8 *codec_caps, uint32 vendor, uint16 codec)
{
    return (vendor == appA2dpConvertUint8ToUint32(&codec_caps[4])) &&
           (codec  == appA2dpConvertUint8ToUint16(&codec_caps[8]));
}


/*****************************************************************************/
static bool isCodecCsrAptx(const uint8 *codec_caps)
{
    return checkVendorCodec(codec_caps, A2DP_APT_VENDOR_ID, A2DP_CSR_APTX_CODEC_ID);
}

static bool isCodecCsrAptxHD(const uint8 *codec_caps)
{
    return checkVendorCodec(codec_caps, A2DP_QTI_VENDOR_ID, A2DP_QTI_APTXHD_CODEC_ID);
}

bool isCodecAptxAdaptive(const uint8 *codec_caps)
{
    return checkVendorCodec(codec_caps, A2DP_QTI_VENDOR_ID, A2DP_QTI_APTX_AD_CODEC_ID);
}
#endif /* A2DP_SBC_ONLY */

static void clampSbcBitpool(const uint8 *codec_caps, codec_data_type *codec_data)
{
    /* As we support the full SBC range, we limit our bit pool range to the values passed by the other side. */
    /* Store configuration in SBC format */
    (void)appA2dpFindStdEmbeddedCodecCaps(&codec_caps, CSR_BT_AV_SBC);
    codec_data->format = appA2dpSbcFormatFromConfig(codec_caps);

    /* Calculate the optimal bitpool to use for the required data rate */
    if ((codec_data->format & 0x0c) == 0)
    {   /* Mono mode - 1 channel */
        codec_data->bitpool = appA2dpSbcSelectBitpool((uint8)codec_data->format,
                                                      SBC_ONE_CHANNEL_RATE,
                                                      codec_data->packet_size);
    }
    else
    {   /* All other modes are 2 channel */
        codec_data->bitpool = appA2dpSbcSelectBitpool((uint8)codec_data->format,
                                                   SBC_TWO_CHANNEL_RATE,
                                                   codec_data->packet_size);
    }

    /* Clamp bitpool to remote device's limits. TODO: B-4407 we could try and use a lower data rate. */
    if (codec_data->bitpool < codec_caps[6])  /* Min bitpool */
    {
        codec_data->bitpool = codec_caps[6];
    }

    if (codec_data->bitpool > codec_caps[7])  /* Max bitpool */
    {
        codec_data->bitpool = codec_caps[7];
    }
}


/*****************************************************************************/
bool appA2dpSelectOptimalCodecSettings(const avInstanceTaskData *theInst, uint8 *remote_codec_caps)
{
    const a2dpSepConfigType *sep_config = ((a2dpSepDataType *)PanicNull(appA2dpBlockGetCurrent(theInst, DATA_BLOCK_SEP_LIST)))->sep_config;
    const uint8 *local_codec_caps = sep_config->caps;

    if (!appA2dpFindCodecSpecificInformation(&local_codec_caps, 0) ||
        !appA2dpFindCodecSpecificInformation((const uint8**)&remote_codec_caps, 0))
    {   /* Unable to locate actual start of local/remote codec service caps */
        return FALSE;
    }

    /* If this is a sink we need to inform the client of the codec settings */
    if (sep_config->sep_type == CSR_BT_AV_SINK)
    {
        /* Determine the optimal codec settings */
        switch ( local_codec_caps[3] )
        {
            case CSR_BT_AV_SBC:
                DEBUG_LOG("appA2dpSelectOptimalCodecSettings: CSR_BT_AV_SBC\n");
                appA2dpSelectOptimalSbcCapsSink(local_codec_caps, remote_codec_caps);
                return TRUE;

#ifndef A2DP_SBC_ONLY
            case CSR_BT_AV_MPEG24_AAC:
                DEBUG_LOG("appA2dpSelectOptimalCodecSettings: CSR_BT_AV_MPEG24_AAC\n");
                appA2dpSelectOptimalAacCapsSink(local_codec_caps, remote_codec_caps);
                return TRUE;

            case CSR_BT_AV_NON_A2DP_CODEC:
                if (isCodecCsrAptx(local_codec_caps) && isCodecCsrAptx(remote_codec_caps) )
                {
                    DEBUG_LOG("appA2dpSelectOptimalCodecSettings: CSR_BT_AV_NON_A2DP_CODEC: AptX\n");
                    selectOptimalCsrAptxCapsSink(local_codec_caps, remote_codec_caps);
                    return TRUE;
                }
                else if (isCodecCsrAptxHD(local_codec_caps) && isCodecCsrAptxHD(remote_codec_caps) )
                {
                    DEBUG_LOG("appA2dpSelectOptimalCodecSettings: CSR_BT_AV_NON_A2DP_CODEC: AptX-HD\n");
                    selectOptimalCsrAptxHDCapsSink(local_codec_caps, remote_codec_caps);
                    return TRUE;
                }
                else if (isCodecAptxAdaptive(local_codec_caps) && isCodecAptxAdaptive(remote_codec_caps))
                {
                    DEBUG_LOG("appA2dpSelectOptimalCodecSettings: CSR_BT_AV_NON_A2DP_CODEC: AptX Adaptive\n");
                    appA2dpSelectOptimalAptxAdCapsSink(local_codec_caps, remote_codec_caps);
                    return TRUE;
                }
                else
                {
                    DEBUG_LOG("appA2dpSelectOptimalCodecSettings: UNSUPPORTED - [local vendor=0x%lX id=0x%X] [remote vendor=0x%lX id=0x%X]\n",
                               appA2dpConvertUint8ToUint32(&local_codec_caps[4]),
                               ((local_codec_caps[8] << 8) | local_codec_caps[9]),
                               appA2dpConvertUint8ToUint32(&remote_codec_caps[4]),
                               (remote_codec_caps[8] << 8) | remote_codec_caps[9]);
                    return FALSE;
                }
#endif
        default:
            return FALSE;
        }
    }
    else
    {
        DEBUG_LOG_INFO("appA2dpSelectOptimalCodecSettings CSR_BT_AV_SOURCE");
        /* Local device is a source of one type or another */
        switch ( local_codec_caps[3] )
        {
            case CSR_BT_AV_SBC:
                DEBUG_LOG_INFO("appA2dpSelectOptimalCodecSettings CSR_BT_AV_SBC");
                appA2dpSelectOptimalSbcCapsSource(local_codec_caps, remote_codec_caps);
                return TRUE;

#ifndef A2DP_SBC_ONLY
            case CSR_BT_AV_MPEG24_AAC:
                DEBUG_LOG_INFO("appA2dpSelectOptimalCodecSettings CSR_BT_AV_MPEG24_AAC");
                appA2dpSelectOptimalAacCapsSource(local_codec_caps, remote_codec_caps);
                return TRUE;

            case CSR_BT_AV_NON_A2DP_CODEC:
                if (isCodecCsrAptx(local_codec_caps) ) 
                {
                    DEBUG_LOG_INFO("appA2dpSelectOptimalCodecSettings CSR_BT_AV_NON_A2DP_CODEC - AptX");
                    selectOptimalCsrAptxCapsSource(local_codec_caps, remote_codec_caps);
                    return TRUE;
                }
                else if (isCodecCsrAptxHD(local_codec_caps) ) 
                {
                    DEBUG_LOG_INFO("appA2dpSelectOptimalCodecSettings CSR_BT_AV_NON_A2DP_CODEC - AptX-HD");
                    selectOptimalCsrAptxHDCapsSource(local_codec_caps, remote_codec_caps);
                    return TRUE;
                }
                else if (isCodecAptxAdaptive(local_codec_caps) && isCodecAptxAdaptive(remote_codec_caps) )
                {
                    DEBUG_LOG_INFO("appA2dpSelectOptimalCodecSettings CSR_BT_AV_NON_A2DP_CODEC - AptX Adaptive");
                    appA2dpSelectOptimalAptxAdCapsSource(local_codec_caps, remote_codec_caps);
                    return TRUE;
                }
                else
                {
                    DEBUG_LOG_WARN("a2dpSelectOptimalCodecSettings UNSUPPORTED - [local vendor=0x%lX id=0x%X] [remote vendor=0x%lX id=0x%X]",
                                   appA2dpConvertUint8ToUint32(&local_codec_caps[4]),
                                   ((local_codec_caps[8] << 8) | local_codec_caps[9]),
                                   appA2dpConvertUint8ToUint32(&remote_codec_caps[4]),
                                   (remote_codec_caps[8] << 8) | remote_codec_caps[9]);
                    return FALSE;
                }
#endif
            default:
                return FALSE;
        }
    }
}

/*****************************************************************************/
a2dp_codec_settings *appA2dpGetCodecAudioParams(const avInstanceTaskData *theInst)
{
    a2dpSepDataType *current_sep;
    const uint8 *codec_caps;
    const uint8 *service_caps;
    uint16 size_service_caps;
    a2dp_codec_settings *codec_settings;

    if ((codec_caps = (service_caps = appA2dpBlockGetBase(theInst, DATA_BLOCK_CONFIGURED_SERVICE_CAPS))) == NULL)
    {
        return NULL;
    }

    size_service_caps = appA2dpBlockGetSize(theInst, DATA_BLOCK_CONFIGURED_SERVICE_CAPS);
    codec_settings = (a2dp_codec_settings *)PanicNull(malloc(sizeof(a2dp_codec_settings) + size_service_caps) );
    memset(codec_settings, 0, sizeof(a2dp_codec_settings) + size_service_caps);  /* Zero the Codec settings */
    codec_settings->size_configured_codec_caps = size_service_caps;
    memmove(codec_settings->configured_codec_caps, service_caps, size_service_caps);

    codec_settings->codecData.content_protection = appA2dpGetContentProtection(service_caps,
                                                                               size_service_caps,
                                                                               NULL);
    codec_settings->codecData.latency_reporting = appA2dpIsServiceSupported((uint8)CSR_BT_AV_SC_DELAY_REPORTING,
                                                                            service_caps,
                                                                            size_service_caps);

    if (!appA2dpFindCodecSpecificInformation(&codec_caps, &size_service_caps))
    {
        free(codec_settings);
        return NULL;
    }

    if ((current_sep = (a2dpSepDataType *)appA2dpBlockGetCurrent(theInst, DATA_BLOCK_SEP_LIST)) == NULL)
    {
        free(codec_settings);
        return NULL;
    }

    codec_settings->seid = current_sep->sep_config->seid;
    codec_settings->sink = theInst->a2dp.media_sink;
    codec_settings->remote_mtu = theInst->a2dp.mtu;

    /* Initially assume that packet size can be no larger than the remote device's media channel MTU.
       Whilst this may not strictly be true in all cases (e.g. for sinks, the source device's receive
       MTU is irrelevant), codec-specific config will override the value as required anyway. */
    codec_settings->codecData.packet_size = theInst->a2dp.mtu;

    switch (codec_caps[3])
    {
    case CSR_BT_AV_SBC:
        clampSbcBitpool(codec_caps, &codec_settings->codecData);
        appA2dpGetSbcConfigSettings(codec_caps, codec_settings);
        break;

#ifndef A2DP_SBC_ONLY
    case CSR_BT_AV_MPEG24_AAC:
        appA2dpGetAacConfigSettings(codec_caps, codec_settings);
        break;

    case CSR_BT_AV_NON_A2DP_CODEC:
        if (isCodecCsrAptx(codec_caps))
        {
            /* Get the config settings so they can be sent to the client */
            getCsrAptxConfigSettings(codec_caps, codec_settings);
        }
        else if (isCodecCsrAptxHD(codec_caps))
        {
            /* Get the config settings so they can be sent to the client */
            getCsrAptxHDConfigSettings(codec_caps, codec_settings);
        }
        else if (isCodecAptxAdaptive(codec_caps))
        {
            /* Get the config settings so they can be sent to the client */
            appA2dpGetAptxAdConfigSettings(codec_caps, codec_settings);
        }

        break;
#endif

    default:
        free(codec_settings);
        return NULL;
    }

    /* Tell the client so it can configure the codec */
    return codec_settings;
}

